/*-------------------------------------------------------------------------
 *
 * copy.c
 *		实现 COPY 实用命令
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/copy.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>

#include "access/sysattr.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_authid.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "executor/executor.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteHandler.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/rls.h"
#ifdef FDD
#include "rewrite/rowsecurity.h"
#endif

/*
 * DoCopy 执行 SQL COPY 语句
 *
 * 根据 <from> 的值，从表 <relation> 中卸载或重新加载内容。
 * (<from> = true 表示我们正在插入到表中。) 在 "TO" 情况下
 * 我们还支持复制任意 SELECT、INSERT、UPDATE 或 DELETE 查询的输出。
 *
 * 如果 <pipe> 为 false，传输在表和名为 <filename> 的文件之间进行。
 * 否则，传输在表和我们的常规输入/输出流之间进行。后者可以是 stdin/stdout 或一个
 * 套接字，具体取决于我们是否在 Postmaster 控制下运行。
 *
 * 不允许没有 'pg_read_server_files' 或
 * 'pg_write_server_files' 角色的 Postgres 用户从文件中读取或写入。
 *
 * 如果用户没有适当的权限访问表或特别请求的列，则不允许进行复制。
 */
void DoCopy(ParseState *fc_pstate, const CopyStmt *fc_stmt,
	   int fc_stmt_location, int fc_stmt_len,
	   uint64 *fc_processed)
{
	bool		fc_is_from = fc_stmt->is_from;
	bool		fc_pipe = (fc_stmt->filename == NULL);
	Relation	fc_rel;
	Oid			fc_relid;
	RawStmt    *fc_query = NULL;
	Node	   *fc_whereClause = NULL;

	/*
	 * 除了拥有适当角色的用户外，不允许文件或程序的 COPY 操作。
	 */
	if (!fc_pipe)
	{
		if (fc_stmt->is_program)
		{
			if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
						 errhint("Anyone can COPY to stdout or from stdin. "
								 "psql's \\copy command also works for anyone.")));
		}
		else
		{
			if (fc_is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
						 errhint("Anyone can COPY to stdout or from stdin. "
								 "psql's \\copy command also works for anyone.")));

			if (!fc_is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
						 errhint("Anyone can COPY to stdout or from stdin. "
								 "psql's \\copy command also works for anyone.")));
		}
	}

	if (fc_stmt->relation)
	{
		LOCKMODE	fc_lockmode = fc_is_from ? RowExclusiveLock : AccessShareLock;
		ParseNamespaceItem *fc_nsitem;
		RangeTblEntry *fc_rte;
		TupleDesc	fc_tupDesc;
		List	   *fc_attnums;
		ListCell   *fc_cur;

		Assert(!fc_stmt->query);

		/* 打开并锁定关系，使用适当的锁定类型。 */
		fc_rel = table_openrv(fc_stmt->relation, fc_lockmode);

		fc_relid = RelationGetRelid(fc_rel);

		fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel, fc_lockmode,
											   NULL, false, false);
		fc_rte = fc_nsitem->p_rte;
		fc_rte->requiredPerms = (fc_is_from ? ACL_INSERT : ACL_SELECT);

		if (fc_stmt->whereClause)
		{
			/* 将 nsitem 添加到查询命名空间 */
			addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

			/* 转换原始表达式树 */
			fc_whereClause = transformExpr(fc_pstate, fc_stmt->whereClause, EXPR_KIND_COPY_WHERE);

			/* 确保它产生布尔结果。 */
			fc_whereClause = coerce_to_boolean(fc_pstate, fc_whereClause, "WHERE");

			/* 我们还必须修复它的排序规则 */
			assign_expr_collations(fc_pstate, fc_whereClause);

			fc_whereClause = eval_const_expressions(NULL, fc_whereClause);

			fc_whereClause = (Node *) canonicalize_qual((Expr *) fc_whereClause, false);
			fc_whereClause = (Node *) make_ands_implicit((Expr *) fc_whereClause);
		}

		fc_tupDesc = RelationGetDescr(fc_rel);
		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_rel, fc_stmt->attlist);
		foreach(fc_cur, fc_attnums)
		{
			int			fc_attno = lfirst_int(fc_cur) -
			FirstLowInvalidHeapAttributeNumber;

			if (fc_is_from)
				fc_rte->insertedCols = bms_add_member(fc_rte->insertedCols, fc_attno);
			else
				fc_rte->selectedCols = bms_add_member(fc_rte->selectedCols, fc_attno);
		}
		ExecCheckRTPerms(fc_pstate->p_rtable, true);

		/*
		 * 行安全策略的权限检查。
		 *
		 * 如果用户请求了无效的内容，check_enable_rls 将用 ereport(ERROR) 报告错误，
		 * 否则将指示我们是否应该为此 COPY 语句启用 RLS（返回 RLS_ENABLED）。
		 *
		 * 如果关系有行安全策略并且我们需要应用它，
		 * 则执行 "query" 复制，并允许正常查询处理来处理这些策略。
		 *
		 * 如果 RLS 未为此启用，则正常处理非过滤关系。
		 */
#ifdef FDD
		if ((check_enable_rls(fc_rte->relid, InvalidOid, false) == RLS_ENABLED) || 
			fdb_need_mac(fc_rte->relid, NULL))
#else
		if (check_enable_rls(fc_rte->relid, InvalidOid, false) == RLS_ENABLED)
#endif
		{
			SelectStmt *fc_select;
			ColumnRef  *fc_cr;
			ResTarget  *fc_target;
			RangeVar   *fc_from;
			List	   *fc_targetList = NIL;

			if (fc_is_from)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("COPY FROM not supported with row-level security"),
						 errhint("Use INSERT statements instead.")));

			/*
			 * 构建目标列表
			 *
			 * 如果在 COPY 命令的属性列表中未指定列，则目标列表包含 'all' 列。
			 * 因此，应将 '*' 作为结果 SELECT 语句的目标列表。
			 *
			 * 如果在属性列表中指定了列，则为每列创建 ColumnRef 和 ResTarget，
			 * 并将它们添加到结果 SELECT 语句的目标列表中。
			 */
			if (!fc_stmt->attlist)
			{
				fc_cr = makeNode(ColumnRef);
				fc_cr->fields = list_make1(makeNode(A_Star));
				fc_cr->location = -1;

				fc_target = makeNode(ResTarget);
				fc_target->name = NULL;
				fc_target->indirection = NIL;
				fc_target->val = (Node *) fc_cr;
				fc_target->location = -1;

				fc_targetList = list_make1(fc_target);
			}
			else
			{
				ListCell   *fc_lc;

				foreach(fc_lc, fc_stmt->attlist)
				{
					/*
					 * 为每个列构建 ColumnRef。 ColumnRef
					 * 的 'fields' 属性是一个字符串节点，分别对应于
					 * 列名。
					 */
					fc_cr = makeNode(ColumnRef);
					fc_cr->fields = list_make1(lfirst(fc_lc));
					fc_cr->location = -1;

					/* 构建 ResTarget 并将 ColumnRef 添加到其中。 */
					fc_target = makeNode(ResTarget);
					fc_target->name = NULL;
					fc_target->indirection = NIL;
					fc_target->val = (Node *) fc_cr;
					fc_target->location = -1;

					/* 将每列添加到 SELECT 语句的目标列表 */
					fc_targetList = lappend(fc_targetList, fc_target);
				}
			}

			/*
			 * 为 from 子句构建 RangeVar，基于我们已打开和锁定的
			 * 关系进行完全限定。使用 "ONLY"，以便
			 * COPY 仅从目标表中检索行，而不从任何
			 * 继承子表中检索，正如 RLS 不适用时的情况。
			 */
			fc_from = makeRangeVar(get_namespace_name(RelationGetNamespace(fc_rel)),
								pstrdup(RelationGetRelationName(fc_rel)),
								-1);
			fc_from->inh = false;	/* 应用 ONLY */

			/* 构建查询 */
			fc_select = makeNode(SelectStmt);
			fc_select->targetList = fc_targetList;
			fc_select->fromClause = list_make1(fc_from);

			fc_query = makeNode(RawStmt);
			fc_query->stmt = (Node *) fc_select;
			fc_query->stmt_location = fc_stmt_location;
			fc_query->stmt_len = fc_stmt_len;

			/*
			 * 目前关闭该关系，但保持锁定以防止在我们开始基于查询的COPY
			 * 之间发生更改。
			 *
			 * 我们将在稍后作为基于查询的COPY的一部分重新打开它。
			 */
			table_close(fc_rel, NoLock);
			fc_rel = NULL;
		}
	}
	else
	{
		Assert(fc_stmt->query);

		/* MERGE 被解析器允许，但未实现。暂时拒绝 */
		if (IsA(fc_stmt->query, MergeStmt))
			ereport(ERROR,
					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					errmsg("MERGE not supported in COPY"));

		fc_query = makeNode(RawStmt);
		fc_query->stmt = fc_stmt->query;
		fc_query->stmt_location = fc_stmt_location;
		fc_query->stmt_len = fc_stmt_len;

		fc_relid = InvalidOid;
		fc_rel = NULL;
	}

	if (fc_is_from)
	{
		CopyFromState fc_cstate;

		Assert(fc_rel);

		/* 检查只读事务和并行模式 */
		if (XactReadOnly && !fc_rel->rd_islocaltemp)
			PreventCommandIfReadOnly("COPY FROM");

		fc_cstate = BeginCopyFrom(fc_pstate, fc_rel, fc_whereClause,
							   fc_stmt->filename, fc_stmt->is_program,
							   NULL, fc_stmt->attlist, fc_stmt->options);
		*fc_processed = CopyFrom(fc_cstate);	/* 从文件复制到数据库 */
		EndCopyFrom(fc_cstate);
	}
	else
	{
		CopyToState fc_cstate;

		fc_cstate = BeginCopyTo(fc_pstate, fc_rel, fc_query, fc_relid,
							 fc_stmt->filename, fc_stmt->is_program,
							 fc_stmt->attlist, fc_stmt->options);
		*fc_processed = DoCopyTo(fc_cstate);	/* 从数据库复制到文件 */
		EndCopyTo(fc_cstate);
	}

	if (fc_rel != NULL)
		table_close(fc_rel, NoLock);
}

/*
 * 从DefElem中提取CopyHeaderChoice值。这类似于
 * defGetBoolean()，但也接受特殊值“match”。
 */
static CopyHeaderChoice
fc_defGetCopyHeaderChoice(DefElem *fc_def, bool fc_is_from)
{
	/*
	 * 如果未给出参数，则假定为“true”。
	 */
	if (fc_def->arg == NULL)
		return COPY_HEADER_TRUE;

	/*
	 * 允许 0、1、“true”、“false”、“on”、“off”或“match”。
	 */
	switch (nodeTag(fc_def->arg))
	{
		case T_Integer:
			switch (intVal(fc_def->arg))
			{
				case 0:
					return COPY_HEADER_FALSE;
				case 1:
					return COPY_HEADER_TRUE;
				default:
					/* 否则，在下面报错 */
					break;
			}
			break;
		default:
			{
				char	   *fc_sval = defGetString(fc_def);

				/*
				 * 此处接受的字符串集合应该与
				 * 语法的opt_boolean_or_string产生匹配。
				 */
				if (pg_strcasecmp(fc_sval, "true") == 0)
					return COPY_HEADER_TRUE;
				if (pg_strcasecmp(fc_sval, "false") == 0)
					return COPY_HEADER_FALSE;
				if (pg_strcasecmp(fc_sval, "on") == 0)
					return COPY_HEADER_TRUE;
				if (pg_strcasecmp(fc_sval, "off") == 0)
					return COPY_HEADER_FALSE;
				if (pg_strcasecmp(fc_sval, "match") == 0)
				{
					if (!fc_is_from)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("cannot use \"%s\" with HEADER in COPY TO",
										fc_sval)));
					return COPY_HEADER_MATCH;
				}
			}
			break;
	}
	ereport(ERROR,
			(errcode(ERRCODE_SYNTAX_ERROR),
			 errmsg("%s requires a Boolean value or \"match\"",
					fc_def->defname)));
	return COPY_HEADER_FALSE;	/* 保持编译器安静 */
}

/*
 * 处理COPY的语句选项列表。
 *
 * 扫描选项列表（DefElem列表）并将信息转置到*opts_out中，
 * 应用适当的错误检查。
 *
 * 如果'opts_out'不为NULL，则假定其最初填充为零。
 *
 * 这是导出的，以便COPY API的外部用户可以进行合理性检查
 * 选项列表。在这种用法中，'opts_out'可以传递为NULL，收集的数据
 * 只是泄漏，直到CurrentMemoryContext被重置。
 *
 * 请注意，像FORCE QUOTE中列出的列名是否实际存在等附加检查
 * 必须在稍后应用。这只是检查选项列表的自我一致性。
 */
void ProcessCopyOptions(ParseState *fc_pstate,
				   CopyFormatOptions *fc_opts_out,
				   bool fc_is_from,
				   List *fc_options)
{
	bool		fc_format_specified = false;
	bool		fc_freeze_specified = false;
	bool		fc_header_specified = false;
	ListCell   *fc_option;

	/* 支持外部使用以进行选项合理性检查 */
	if (fc_opts_out == NULL)
		fc_opts_out = (CopyFormatOptions *) palloc0(sizeof(CopyFormatOptions));

	fc_opts_out->file_encoding = -1;

	/* 从语句节点树中提取选项 */
	foreach(fc_option, fc_options)
	{
		DefElem    *fc_defel = lfirst_node(DefElem, fc_option);

		if (strcmp(fc_defel->defname, "format") == 0)
		{
			char	   *fc_fmt = defGetString(fc_defel);

			if (fc_format_specified)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_format_specified = true;
			if (strcmp(fc_fmt, "text") == 0)
				 /* 默认格式 */ ;
			else if (strcmp(fc_fmt, "csv") == 0)
				fc_opts_out->csv_mode = true;
			else if (strcmp(fc_fmt, "binary") == 0)
				fc_opts_out->binary = true;
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("COPY format \"%s\" not recognized", fc_fmt),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else if (strcmp(fc_defel->defname, "freeze") == 0)
		{
			if (fc_freeze_specified)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_freeze_specified = true;
			fc_opts_out->freeze = defGetBoolean(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "delimiter") == 0)
		{
			if (fc_opts_out->delim)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->delim = defGetString(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "null") == 0)
		{
			if (fc_opts_out->null_print)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->null_print = defGetString(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "header") == 0)
		{
			if (fc_header_specified)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_header_specified = true;
			fc_opts_out->header_line = fc_defGetCopyHeaderChoice(fc_defel, fc_is_from);
		}
		else if (strcmp(fc_defel->defname, "quote") == 0)
		{
			if (fc_opts_out->quote)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->quote = defGetString(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "escape") == 0)
		{
			if (fc_opts_out->escape)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->escape = defGetString(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "force_quote") == 0)
		{
			if (fc_opts_out->force_quote || fc_opts_out->force_quote_all)
				errorConflictingDefElem(fc_defel, fc_pstate);
			if (fc_defel->arg && IsA(fc_defel->arg, A_Star))
				fc_opts_out->force_quote_all = true;
			else if (fc_defel->arg && IsA(fc_defel->arg, List))
				fc_opts_out->force_quote = castNode(List, fc_defel->arg);
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument to option \"%s\" must be a list of column names",
								fc_defel->defname),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else if (strcmp(fc_defel->defname, "force_not_null") == 0)
		{
			if (fc_opts_out->force_notnull)
				errorConflictingDefElem(fc_defel, fc_pstate);
			if (fc_defel->arg && IsA(fc_defel->arg, List))
				fc_opts_out->force_notnull = castNode(List, fc_defel->arg);
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument to option \"%s\" must be a list of column names",
								fc_defel->defname),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else if (strcmp(fc_defel->defname, "force_null") == 0)
		{
			if (fc_opts_out->force_null)
				errorConflictingDefElem(fc_defel, fc_pstate);
			if (fc_defel->arg && IsA(fc_defel->arg, List))
				fc_opts_out->force_null = castNode(List, fc_defel->arg);
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument to option \"%s\" must be a list of column names",
								fc_defel->defname),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else if (strcmp(fc_defel->defname, "convert_selectively") == 0)
		{
			/*
			 * 未记录，不可通过SQL访问的选项：仅将
			 * 命名列转换为二进制形式，其余以NULL存储。允许列列表为NIL。
			 */
			if (fc_opts_out->convert_selectively)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->convert_selectively = true;
			if (fc_defel->arg == NULL || IsA(fc_defel->arg, List))
				fc_opts_out->convert_select = castNode(List, fc_defel->arg);
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument to option \"%s\" must be a list of column names",
								fc_defel->defname),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else if (strcmp(fc_defel->defname, "encoding") == 0)
		{
			if (fc_opts_out->file_encoding >= 0)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_opts_out->file_encoding = pg_char_to_encoding(defGetString(fc_defel));
			if (fc_opts_out->file_encoding < 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument to option \"%s\" must be a valid encoding name",
								fc_defel->defname),
						 parser_errposition(fc_pstate, fc_defel->location)));
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("option \"%s\" not recognized",
							fc_defel->defname),
					 parser_errposition(fc_pstate, fc_defel->location)));
	}

	/*
	 * 检查不兼容的选项（在插入默认值之前必须执行这两个检查）
	 */
	if (fc_opts_out->binary && fc_opts_out->delim)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot specify DELIMITER in BINARY mode")));

	if (fc_opts_out->binary && fc_opts_out->null_print)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot specify NULL in BINARY mode")));

	/* 为省略的选项设置默认值 */
	if (!fc_opts_out->delim)
		fc_opts_out->delim = fc_opts_out->csv_mode ? "," : "\t";

	if (!fc_opts_out->null_print)
		fc_opts_out->null_print = fc_opts_out->csv_mode ? "" : "\\N";
	fc_opts_out->null_print_len = strlen(fc_opts_out->null_print);

	if (fc_opts_out->csv_mode)
	{
		if (!fc_opts_out->quote)
			fc_opts_out->quote = "\"";
		if (!fc_opts_out->escape)
			fc_opts_out->escape = fc_opts_out->quote;
	}

	/* 仅支持单字节分隔符字符串。 */
	if (strlen(fc_opts_out->delim) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY delimiter must be a single one-byte character")));

	/* 禁止换行符 */
	if (strchr(fc_opts_out->delim, '\r') != NULL ||
		strchr(fc_opts_out->delim, '\n') != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("COPY delimiter cannot be newline or carriage return")));

	if (strchr(fc_opts_out->null_print, '\r') != NULL ||
		strchr(fc_opts_out->null_print, '\n') != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("COPY null representation cannot use newline or carriage return")));

	/*
	 * 在非CSV模式下禁止不安全的分隔符字符。我们不能允许
	 * 反斜杠，因为它会导致歧义。我们不能允许其他
	 * 情况，因为与分隔符匹配的数据字符必须
	 * 反斜杠，并且某些反斜杠组合在COPY IN中以非字面形式解释。
	 * 禁用所有小写ASCII字母显得过于严格，但为了一致性和
	 * 未来的可用性似乎是最佳选择。同样，尽管只有八进制
	 * 数字确实是危险的，但我们也会禁止所有数字。
	 */
	if (!fc_opts_out->csv_mode &&
		strchr("\\.abcdefghijklmnopqrstuvwxyz0123456789",
			   fc_opts_out->delim[0]) != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("COPY delimiter cannot be \"%s\"", fc_opts_out->delim)));

	/* 检查头部 */
	if (fc_opts_out->binary && fc_opts_out->header_line)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot specify HEADER in BINARY mode")));

	/* 检查引号 */
	if (!fc_opts_out->csv_mode && fc_opts_out->quote != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY quote available only in CSV mode")));

	if (fc_opts_out->csv_mode && strlen(fc_opts_out->quote) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY quote must be a single one-byte character")));

	if (fc_opts_out->csv_mode && fc_opts_out->delim[0] == fc_opts_out->quote[0])
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("COPY delimiter and quote must be different")));

	/* 检查转义 */
	if (!fc_opts_out->csv_mode && fc_opts_out->escape != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY escape available only in CSV mode")));

	if (fc_opts_out->csv_mode && strlen(fc_opts_out->escape) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY escape must be a single one-byte character")));

	/* 检查强制引号 */
	if (!fc_opts_out->csv_mode && (fc_opts_out->force_quote || fc_opts_out->force_quote_all))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force quote available only in CSV mode")));
	if ((fc_opts_out->force_quote || fc_opts_out->force_quote_all) && fc_is_from)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force quote only available using COPY TO")));

	/* 检查强制非NULL */
	if (!fc_opts_out->csv_mode && fc_opts_out->force_notnull != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force not null available only in CSV mode")));
	if (fc_opts_out->force_notnull != NIL && !fc_is_from)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force not null only available using COPY FROM")));

	/* 检查强制NULL */
	if (!fc_opts_out->csv_mode && fc_opts_out->force_null != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force null available only in CSV mode")));

	if (fc_opts_out->force_null != NIL && !fc_is_from)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY force null only available using COPY FROM")));

	/* 不允许分隔符出现在NULL字符串中。 */
	if (strchr(fc_opts_out->null_print, fc_opts_out->delim[0]) != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("COPY delimiter must not appear in the NULL specification")));

	/* 不允许在空字符串中出现CSV引号字符。 */
	if (fc_opts_out->csv_mode &&
		strchr(fc_opts_out->null_print, fc_opts_out->quote[0]) != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("CSV quote character must not appear in the NULL specification")));
}

/*
 * CopyGetAttnums - 构建要复制的attnum整数列表
 *
 * 输入的attnamelist是用户指定的列列表，
 * 或者如果没有，则为NIL（在这种情况下，我们想要所有未被丢弃的
 * 列）。
 *
 * 我们不在生成的完整列表中包含生成的列，也不允许
 * 明确指定它们。它们对于COPY FROM没有意义，
 * 但我们可能会允许它们用于COPY TO。但是这样至少可以确保
 * 我们复制出去的任何东西都可以复制回来。
 *
 * rel可以为NULL...它仅用于错误报告。
 */
List * CopyGetAttnums(TupleDesc fc_tupDesc, Relation fc_rel, List *fc_attnamelist)
{
	List	   *fc_attnums = NIL;

	if (fc_attnamelist == NIL)
	{
		/* 生成默认列列表 */
		int			fc_attr_count = fc_tupDesc->natts;
		int			fc_i;

		for (fc_i = 0; fc_i < fc_attr_count; fc_i++)
		{
			if (TupleDescAttr(fc_tupDesc, fc_i)->attisdropped)
				continue;
			if (TupleDescAttr(fc_tupDesc, fc_i)->attgenerated)
				continue;
			fc_attnums = lappend_int(fc_attnums, fc_i + 1);
		}
	}
	else
	{
		/* 验证用户提供的列表并提取attnum */
		ListCell   *fc_l;

		foreach(fc_l, fc_attnamelist)
		{
			char	   *fc_name = strVal(lfirst(fc_l));
			int			fc_attnum;
			int			fc_i;

			/* 查找列名 */
			fc_attnum = InvalidAttrNumber;
			for (fc_i = 0; fc_i < fc_tupDesc->natts; fc_i++)
			{
				Form_pg_attribute fc_att = TupleDescAttr(fc_tupDesc, fc_i);

				if (fc_att->attisdropped)
					continue;
				if (namestrcmp(&(fc_att->attname), fc_name) == 0)
				{
					if (fc_att->attgenerated)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
								 errmsg("column \"%s\" is a generated column",
										fc_name),
								 errdetail("Generated columns cannot be used in COPY.")));
					fc_attnum = fc_att->attnum;
					break;
				}
			}
			if (fc_attnum == InvalidAttrNumber)
			{
				if (fc_rel != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" of relation \"%s\" does not exist",
									fc_name, RelationGetRelationName(fc_rel))));
				else
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" does not exist",
									fc_name)));
			}
			/* 检查重复 */
			if (list_member_int(fc_attnums, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_DUPLICATE_COLUMN),
						 errmsg("column \"%s\" specified more than once",
								fc_name)));
			fc_attnums = lappend_int(fc_attnums, fc_attnum);
		}
	}

	return fc_attnums;
}
